package com.rslai.commons.http;

import com.rslai.commons.http.tools.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.*;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import com.rslai.commons.http.data.HttpBinaryResult;
import com.rslai.commons.http.data.HttpParameter;
import com.rslai.commons.http.data.HttpResult;
import com.rslai.commons.http.util.StringUtil;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;

/**
 * 包装后的 http client
 * Created by lrs on 2018/8/6.
 */
public class RslaiHttpClient extends DefaultHttpClient {
    private final Log log = LogFactory.getLog(getClass());
    private String userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";

    /**
     * 构造方法
     */
    public RslaiHttpClient() {
        this(null);
    }

    /**
     * 构造方法
     * @param conman ClientConnectionManager
     * @param params HttpParams
     */
    public RslaiHttpClient(ClientConnectionManager conman, HttpParams params) {
        super(conman, params);
    }

    /**
     * 构造方法
     * @param params HttpParams
     */
    public RslaiHttpClient(HttpParams params) {
        this(null, params);
    }

    /**
     * 设置调试模式
     * @param debug true：调试，false：不调试
     */
    public void setDebug(boolean debug) {
        Interceptors.debug(this, debug);
    }

    /**
     * 设置代理服务器
     * @param host 服务器地址
     * @param port 端口号
     */
    public void setProxy(String host, int port) {
        HttpHost proxy = new HttpHost(host, port);
        this.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    }

    /**
     * 删除代理服务器
     */
    public void removeProxy() {
        this.getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY);
    }

    /**
     * 获取 http client 的 userAgent
     * @return UserAgent
     */
    public String getUserAgent() {
        return userAgent;
    }

    /**
     * 设置 http client 的 useragent
     * @param userAgent userAgent
     */
    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    /**
     * 设置最大连接数
     * @param max 最大连接数
     */
    public void setMaxTotal(int max) {
        ((ThreadSafeClientConnManager) this.getConnectionManager()).setMaxTotal(max);
    }

    /**
     * 设置 route 的默认最大连接数
     * @param max 最大连接数
     */
    public void setDefaultMaxPerRoute(int max) {
        ((ThreadSafeClientConnManager) this.getConnectionManager()).setDefaultMaxPerRoute(max);
    }

    /**
     * 添加一个 request cookies 拦截器，把 request 中的cookies信息存入 context
     * 需要保存 cookies 的时候需要手动调用此方法，之后可以使用 cookie sstore 存储
     */
    public void setRequestGlobalCookies() {
        this.addRequestInterceptor(RequestCookiesInterceptor.instance);
    }

    /**
     * 删除 request cookies 拦截器
     */
    public void removeRequestGlobalCookies() {
        this.removeResponseInterceptorByClass(RequestCookiesInterceptor.instance.getClass());
    }

    /**
     * 设置浏览器兼容模式的 cookie 策略
     */
    public void allowCookiePolicy() {
        this.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
    }

    /**
     * 设置忽略 cookies
     */
    public void disallowCookiePolicy() {
        this.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
    }

    /**
     * 创建一个支持 cookies 存储的 HttpContext
     * @return HttpContext
     */
    public static HttpContext createCookiesContext() {
        CookieStore cookieStore = new BasicCookieStore();
        HttpContext context = new BasicHttpContext();
        context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
        return context;
    }

    /**
     * 创建一个 RslaiHttp,
     * 默认设置：连接超时 100ms，读取数据超时 1000ms，最大连接200，route最大连接50
     * <p>
     * 例如：
     *      private static final RslaiHttpClient client = RslaiHttp.createDefaultClient();
     * </p>
     * 不用的时候调用关闭方法 close
     * @return RslaiHttp RslaiHttp
     */
    public static RslaiHttpClient createDefaultClient() {
        return createDefaultClient(10000, 10000, 200, 50);
    }

    /**
     * 创建一个线程安全的 RslaiHttp
     * <p>
     * 例如：
     *      private static final RslaiHttpClient client = RslaiHttp.createDefaultClient(10000, 10000, 200, 50);
     * </p>
     * 不用的时候调用关闭方法 close
     * @param connTimeout 连接超时时间 ms
     * @param readTimeout 读取数据超时时间 ms
     * @param maxTotal 最大连接数
     * @param MaxPerRoute route最大连接数
     * @return RslaiHttp RslaiHttp
     */
    public static RslaiHttpClient createDefaultClient(int connTimeout, int readTimeout, int maxTotal, int MaxPerRoute) {
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params, connTimeout); // 设置连接超时时间
        HttpConnectionParams.setSoTimeout(params, readTimeout); // 设置读取数据超时时间
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); // 设置HttpVersion
        HttpProtocolParams.setUserAgent(params, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
        HttpClientParams.setCookiePolicy(params, CookiePolicy.IGNORE_COOKIES);
        // HttpClientParams.setRedirecting(params, false);
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        SSLSocketFactory sslSocketFactory = SSLFactory.getIgnoreSSLCheckSocketFactory();
        schemeRegistry.register(new Scheme("https", 443, sslSocketFactory));
        final X509HostnameVerifier delegate = sslSocketFactory.getHostnameVerifier();
        if (!(delegate instanceof SSLVerifier)) {
            sslSocketFactory.setHostnameVerifier(new SSLVerifier(delegate));
        }
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry, 1, TimeUnit.MINUTES);
        cm.setMaxTotal(maxTotal);
        cm.setDefaultMaxPerRoute(MaxPerRoute);
        RslaiHttpClient client = new RslaiHttpClient(cm, params);
        Strategies.keepAlive(client, 30000); // 设置 keepalive 时间 30000ms
        Interceptors.gzip(client, true); // 设置使用 gzip
        return client;
    }

    /**
     * 通过HttpUriRequest执行http请求
     * 返回数据的编码使用自动探测
     * @param request HttpUriRequest
     * @return 返回 http 结果实体（字符串 content）
     * @throws IOException IOException
     */
    public HttpResult httpExecute(HttpUriRequest request) throws IOException {
        return this.httpExecute(request, null);
    }

    /**
     * 通过HttpUriRequest执行http请求
     * @param request HttpUriRequest
     * @param charset 对方url页面的编码，不为null可以免去自动探测的性能消耗，如果为null，则使用自动探测
     * @return 返回 http 结果实体（字符串 content）
     * @throws IOException IOException
     */
    public HttpResult httpExecute(HttpUriRequest request, String charset) throws IOException {
        return httpExecute(request, null, charset);
    }

    /**
     * 通过HttpUriRequest执行http请求
     * @param request HttpUriRequest
     * @param context HttpContext
     * @param charset 对方url页面的编码，不为null可以免去自动探测的性能消耗，如果为null，则使用自动探测
     * @return 返回 http 结果实体（字符串 content）
     * @throws IOException IOException
     */
    public HttpResult httpExecute(HttpUriRequest request, HttpContext context, String charset) throws IOException {
        HttpEntity entity = null;
        HttpResult result = new HttpResult();
        try {
            HttpResponse response = this.execute(request, context);
            StatusLine status = response.getStatusLine();
            entity = response.getEntity();
            result.setHeaders(response.getAllHeaders());
            result.setStatusCode(status.getStatusCode());
            if (status != null && status.getStatusCode() == 200) {
                String content = StringUtil.toString(entity, charset);
                entity = null;
                result.setContent(content);
            } else if (status != null && status.getStatusCode() == 302) {
                // post 返回 302 后自动跳转
                String redirectURL = response.getFirstHeader("location").getValue();
                HttpGet redirectGet = new HttpGet(redirectURL);
                result = httpExecute(redirectGet, null, charset);
            } else {
                log.warn("can't post url: " + request.getURI() + "   " + status);
            }
        } finally {
            EntityUtils.consume(entity);
        }
        return result;
    }

    /**
     * 通过HttpUriRequest执行http请求
     * @param request HttpUriRequest
     * @param context HttpContext
     * @return 返回http 结果实体（二进制数组 content）
     * @throws IOException IOException
     */
    public HttpBinaryResult httpExecuteBinary(HttpUriRequest request, HttpContext context) throws IOException {
        HttpEntity entity = null;
        HttpBinaryResult result = new HttpBinaryResult();
        try {
            HttpResponse response = this.execute(request, context);
            StatusLine status = response.getStatusLine();
            entity = response.getEntity();
            result.setHeaders(response.getAllHeaders());
            result.setStatusCode(status.getStatusCode());
            if (status != null && status.getStatusCode() == 200) {
                byte[] content = StringUtil.toBytes(entity);
                entity = null;
                result.setContent(content);
            } else {
                log.warn("can't post url: " + request.getURI() + "   " + status);
            }
        } finally {
            EntityUtils.consume(entity);
        }
        return result;
    }

    /**
     * post数据到url
     * 返回数据编码使用自动探测
     * @param url url
     * @param parameter HttpParameter
     * @return 返回页面数据
     * @throws IOException IOException
     */
    public String httpPost(String url, HttpParameter parameter) throws IOException {
        return httpPost(url, parameter, null);
    }

    /**
     * post数据到url
     * @param url url
     * @param parameter HttpParameter
     * @param charset 对方url页面的编码，不为null可以免去自动探测的性能消耗，如果为null，则使用自动探测
     * @return 返回页面数据
     * @throws IOException IOException
     */
    public String httpPost(String url, HttpParameter parameter, String charset) throws IOException {
        HttpPost httpost = new HttpPost(url);
        try {
            httpost.setEntity(new UrlEncodedFormEntity(parameter.getNameValuePairs(), HTTP.UTF_8));
        } catch (UnsupportedEncodingException e1) {
            log.warn("httpPost parameter UrlEncodedFormEntity UnsupportedEncodingException:" + e1.getMessage());
            return null;
        }
        return httpExecute(httpost, charset).getContent();

    }

    /**
     * GET方式获取url数据
     * 返回数据编码使用自动探测
     * @param url url
     * @return 返回页面数据
     * @throws IOException IOException
     */
    public String httpGet(String url) throws IOException {
        return httpGet(url, null);
    }

    /**
     * get方式获取url数据
     * @param url url
     * @param charset 对方url页面的编码，不为null可以免去自动探测的性能消耗，如果为null，则使用自动探测
     * @return 返回页面数据
     * @throws IOException IOException
     */
    public String httpGet(String url, String charset) throws IOException {
        HttpGet get = new HttpGet(url);
        return httpExecute(get, charset).getContent();
    }

    /**
     * 关闭httpclient
     */
    public void close() {
        this.getConnectionManager().shutdown();
    }

}
